#pragma once

#include "arp_packet.hpp"
#include "state.hpp"

namespace limbo::arp {

template <typename ArpState> struct ArpRequest {
  using ContextState = ArpRequest;
  using Context = ArpRequest;
  using LowerState = ArpState;
  using LowerContext = typename ArpState::Context;
  using LowerPacket = typename LowerState::Packet;
  using EthernetState = typename ArpState::LowerState;
  using EthernetContext = typename EthernetState::Context;

  inline void init(const ethernet::MacAddress &mac_source,
                   const ip::IPv4Address &ip_source) {
    eth_ctx.source = mac_source;
    eth_ctx.destination = ethernet::MacAddress::broadcast;
    eth_ctx.type = ethernet::EtherType::arp;
    arp_ctx.eth_context = &eth_ctx;
    arp_ctx.operation = Operation::request;
    arp_ctx.mac_source = mac_source;
    arp_ctx.mac_target = ethernet::MacAddress::unknown;
    arp_ctx.ip_source = ip_source;
  }

  Result recv(Chunk buff, LowerState *arp_state) {
    state = arp_state;
    return Result::make_consumed(this, buff);
  }

  inline bool select(const LowerPacket *packet) {
    return (packet->operation == (uint16_t)Operation::request) &&
           (packet->target_ip == arp_ctx.ip_source);
  }

  template <typename Stack>
  Result send(Stack &stack, Chunk buff, const ip::IPv4Address &dest) {
    arp_ctx.ip_target = dest;
    return stack.send(buff, *this, {});
  }

  LowerPacket &get_parsed() const { return state->get_parsed(); }

  EthernetContext eth_ctx;
  LowerContext arp_ctx;
  LowerState *state;
};

template <typename State>
inline auto *get_lower_context(const ArpRequest<State> &ctx) {
  return &ctx.arp_ctx;
}

} // namespace limbo::arp
